package com.cg.leetcode;

import org.junit.Test;

import java.util.*;

/**
 * 752.打开转盘锁
 *
 * @program: LeetCode->LeetCode_752
 * @description: 752.打开转盘锁
 * @author: cg
 * @create: 2021-09-06 20:29
 **/
public class LeetCode_752 {

    @Test
    public void test752() {
        System.out.println(openLock(new String[]{"0201", "0101", "0102", "1212", "2002"}, "0202"));
        System.out.println(openLock(new String[]{"8887", "8889", "8878", "8898", "8788", "8988", "7888", "9888"}, "8888"));
        System.out.println(openLock(new String[]{"0000"}, "8888"));
    }

    /**
     * 你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字： '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转：例如把 '9' 变为 '0'，'0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
     * 锁的初始数字为 '0000' ，一个代表四个拨轮的数字的字符串。
     * 列表 deadends 包含了一组死亡数字，一旦拨轮的数字和列表里的任何一个元素相同，这个锁将会被永久锁定，无法再被旋转。
     * 字符串 target 代表可以解锁的数字，你需要给出解锁需要的最小旋转次数，如果无论如何不能解锁，返回 `-1` 。
     * <p>
     * 示例 1:
     * 输入：deadends = ["0201","0101","0102","1212","2002"], target = "0202"
     * 输出：6
     * 解释：
     * 可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
     * 注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的，
     * 因为当拨动到 "0102" 时这个锁就会被锁定。
     * <p>
     * 示例 2:
     * 输入: deadends = ["8888"], target = "0009"
     * 输出：1
     * 解释：
     * 把最后一位反向旋转一次即可 "0000" -> "0009"。
     * <p>
     * 示例 3:
     * 输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
     * 输出：-1
     * 解释：
     * 无法旋转到目标数字且不被锁定。
     * <p>
     * 示例 4:
     * 输入: deadends = ["0000"], target = "8888"
     * 输出：-1
     * <p>
     * 提示：
     * 1) 1 <= deadends.length <= 500
     * 2) deadends[i].length == 4
     * 3) target.length == 4
     * 4) target 不在 deadends 之中
     * 5) target 和 deadends[i] 仅由若干位数字组成
     *
     * @param deadends
     * @param target
     * @return
     */
    public int openLock(String[] deadends, String target) {
        // 目标为0000
        if ("0000".equals(target)) {
            return 0;
        }
        //死亡数字
        Set<String> dead = new HashSet<>(Arrays.asList(deadends));
        //死亡数字有0000
        if (dead.contains("0000")) {
            return -1;
        }
        //旋转次数
        int temp = 0;
        //广度优先遍历队列
        Deque<String> deque = new ArrayDeque();
        deque.offer("0000");
        //已搜索过数字
        Set<String> seen = new HashSet<>();
        seen.add("0000");
        while (!deque.isEmpty()) {
            // 旋转次数增加
            ++temp;
            int size = deque.size();
            for (int i = 0; i < size; i++) {
                String status = deque.poll();
                // 遍历可每个数字翻转的组合
                for (String str : get(status)) {
                    //死亡数字集合和已搜索过数字集合中不包括该数字
                    if (!dead.contains(str) && !seen.contains(str)) {
                        //如果是目标数字
                        if (str.equals(target)) {
                            return temp;
                        }
                        //不是目标数字则将该数字加入队列,并加入到已搜索过数字集合中
                        deque.offer(str);
                        seen.add(str);
                    }
                }
            }
        }
        return -1;
    }

    public List<String> get(String status) {
        List<String> res = new ArrayList<>();
        char[] chars = status.toCharArray();
        //循环获取每个数字翻转的组合
        for (int i = 0; i < 4; i++) {
            char ch = chars[i];
            //当前数字往前翻转
            chars[i] = (ch == '0' ? '9' : (char) (ch - 1));
            //将往前翻转后的组合加入数组
            res.add(new String(chars));
            //当前数字往后翻转
            chars[i] = (ch == '9' ? '0' : (char) (ch + 1));
            //将往后翻转后的组合加入数组
            res.add(new String(chars));
            //还原当前数字
            chars[i] = ch;
        }
        return res;
    }

}
